package org.jlambda.util;

import static org.jlambda.tuples.Tuple2.tuple;

import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Semaphore;

import org.jlambda.functions.Fun0;
import org.jlambda.functions.Fun1;
import org.jlambda.functions.Fun2;
import org.jlambda.functions.Fun3;
import org.jlambda.tuples.Tuple2;

public class GenericUtils {

	/**
	* Drains in a manner similar to a seq'd MapR but explicitly uses poll and not the iterator.
	* 
	*/
	public static final class Drain<A> extends Fun2<Fun1<A, Void>, Queue<A>, Void>{
		
		@Override
		public Void apply(Fun1<A, Void> f, Queue<A> mq) {
			A obj = null;
			while( ( obj = (A) mq.poll() ) != null){
				f.apply(obj);
			}
			return null;
		}
	}

	/**
	 * Priority call based function.  The registered function, first arg, will be called
	 * based on the number, second arg.  The call of f(a) goes into a priority queue based on the number.
	 * lower prioritys go first.  At most 2 threads will be blocked while dispatching.
	 * 
	 * @author Chris
	 *
	 * @param <A>
	 * @param <Z>
	 */
	public static class PQCall<A,Z> extends Fun3<Fun1<A,Z>, Number, A, Void> {

		private final ConcurrentLinkedQueue<Tuple2<Number, A>> mq = new ConcurrentLinkedQueue<Tuple2<Number, A>>();

		final private Drain< Tuple2<Number, A> > drain = new Drain< Tuple2<Number, A>>();
		
		public Void drainP(Fun1<A, Void> f, Queue<Tuple2<Number, A>> mq) {
			Tuple2<Number, A> obj = null;
			final PriorityQueue<Tuple2<Number, A>> pq = new PriorityQueue<Tuple2<Number, A>>(20, Tuple2.comparator(Tuple2.<Number, A>func1(), Math.numCmp) );
			if (( obj = mq.poll() ) != null){
				pq.add(obj);
			}
			do {
				// always drain from the real queue before trying to call
				drain.apply( new Fun1<Tuple2<Number, A>, Void>(){
						public Void apply(Tuple2<Number, A> part){
							pq.add(part);
							return null;
						}
					},  mq);
			
				// call it
				if ( ( obj = pq.poll() ) != null){
					f.apply(obj.get2());
				}
				
			} while( obj != null);
			return null;
		}
		
		final Semaphore sem = new Semaphore(2);

		// one processes the list, the other waits to process.  If there is one waiting just return.
		final private Fun1<Fun1<A, Z>, Void> dispatcher = new Fun1<Fun1<A, Z>, Void>(){
			@Override
			public Void apply(final Fun1<A, Z> f){

				try{
					if (sem.tryAcquire()) {
						
						// only two in
						synchronized (sem) {
						// force one to wait
							drainP( 
								new Fun1< A, Void>(){
									@Override
									public Void apply(final A val){
										f.apply(val);
										return null;
									}
								}
								, (Queue) mq
							);
						
						}
					}
				} finally {
					sem.release();
				}
				return null;
			}
		};

		@Override
		public Void apply(Fun1<A, Z> f, Number n, A val) {
			dispatch(f, n, val);		
			return null;
		}
		
		// send this tuple to any matching listeners
		public void dispatch(Fun1<A, Z> f, Number n, A val ){
			mq.add(tuple(n, val));
			dispatcher.applyBg(f);
		}

		
	}
	
	
	/**
	 * TODO: should this be absracted?  J2EE container can just remove.
	 */
	private static Timer jTimer = new Timer();
	
	/**
	 * Calls event functions after the first parameter has expired
	 */
	public final static Fun2<Number, Fun0<Void>, Void> timer = new Fun2<Number, Fun0<Void>, Void>(){

		@Override
		public Void apply(final Number p1, final Fun0<Void> p2) {
			jTimer.schedule( new TimerTask(){
				@Override
				public void run() {
					p2.apply();
				}
			}, p1.longValue());
			return null;
		}
		
	};
	
	
}
